Here's a modern, visually appealing HTML page design for the Netty documentation:

```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Netty 网络框架完全指南 | 技术小馆</title>
    <link href="https://cdn.staticfile.org/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
    <link href="https://cdn.staticfile.org/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
    <style>
        body {
            font-family: 'Noto Sans SC', Tahoma, Arial, Roboto, "Droid Sans", "Helvetica Neue", "Droid Sans Fallback", "Heiti SC", "Hiragino Sans GB", Simsun, sans-self;
            color: #333;
            line-height: 1.6;
        }
        h1, h2, h3, h4, h5, h6 {
            font-family: 'Noto Serif SC', serif;
            font-weight: 600;
        }
        .hero-gradient {
            background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
        }
        .code-block {
            background: #2d2d2d;
            border-radius: 8px;
            position: relative;
        }
        .code-block pre {
            margin: 0;
            padding: 1.5rem;
            overflow-x: auto;
        }
        .code-block .lang {
            position: absolute;
            top: 0;
            right: 0;
            background: rgba(255,255,255,0.1);
            color: #fff;
            padding: 0.25rem 0.75rem;
            border-bottom-left-radius: 8px;
            font-size: 0.875rem;
        }
        .component-card {
            transition: all 0.3s ease;
            border-left: 4px solid #4f46e5;
        }
        .component-card:hover {
            transform: translateY(-2px);
            box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
        }
        .diagram-container {
            background: #f8fafc;
            border-radius: 12px;
            padding: 1.5rem;
            margin: 2rem 0;
            box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
        }
        .nav-link {
            position: relative;
        }
        .nav-link::after {
            content: '';
            position: absolute;
            bottom: -2px;
            left: 0;
            width: 0;
            height: 2px;
            background: #4f46e5;
            transition: width 0.3s ease;
        }
        .nav-link:hover::after {
            width: 100%;
        }
    </style>
</head>
<body class="bg-gray-50">
    <!-- Hero Section -->
    <header class="hero-gradient text-white py-20 px-4 sm:px-6 lg:px-8">
        <div class="max-w-7xl mx-auto">
            <div class="text-center">
                <h1 class="text-4xl md:text-5xl font-bold mb-4">Netty 网络框架完全指南</h1>
                <p class="text-xl md:text-2xl max-w-3xl mx-auto opacity-90">
                    高性能、异步事件驱动的网络应用程序框架
                </p>
                <div class="mt-8 flex justify-center space-x-4">
                    <a href="#what-is-netty" class="bg-white text-indigo-600 px-6 py-3 rounded-lg font-medium hover:bg-gray-100 transition duration-300">
                        开始探索 <i class="fas fa-arrow-down ml-2"></i>
                    </a>
                    <a href="http://netty.io" target="_blank" class="border-2 border-white text-white px-6 py-3 rounded-lg font-medium hover:bg-white hover:bg-opacity-10 transition duration-300">
                        官方文档 <i class="fas fa-external-link-alt ml-2"></i>
                    </a>
                </div>
            </div>
        </div>
    </header>

    <!-- Main Content -->
    <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
        <div class="flex flex-col lg:flex-row gap-8">
            <!-- Sidebar Navigation -->
            <aside class="lg:w-64 flex-shrink-0">
                <div class="sticky top-8 bg-white p-6 rounded-xl shadow-md">
                    <h3 class="font-bold text-lg mb-4 text-gray-800">目录</h3>
                    <nav class="space-y-2">
                        <a href="#what-is-netty" class="block nav-link text-gray-700 hover:text-indigo-600">1. Netty是什么</a>
                        <a href="#introduction" class="block nav-link text-gray-700 hover:text-indigo-600">2. Netty简介</a>
                        <a href="#components" class="block nav-link text-gray-700 hover:text-indigo-600">2.1 重要组件</a>
                        <a href="#thread-model" class="block nav-link text-gray-700 hover:text-indigo-600">3. Netty线程模型</a>
                        <a href="#communication" class="block nav-link text-gray-700 hover:text-indigo-600">4. Netty实现通讯</a>
                        <a href="#channel-pipeline" class="block nav-link text-gray-700 hover:text-indigo-600">5. ChannelPipeline</a>
                        <a href="#http-server" class="block nav-link text-gray-700 hover:text-indigo-600">6. HTTP服务器实现</a>
                        <a href="#ftp-server" class="block nav-link text-gray-700 hover:text-indigo-600">7. FTP服务器实现</a>
                        <a href="#websocket" class="block nav-link text-gray-700 hover:text-indigo-600">8. WebSocket实现</a>
                        <a href="#springboot" class="block nav-link text-gray-700 hover:text-indigo-600">9. SpringBoot整合</a>
                        <a href="#user-management" class="block nav-link text-gray-700 hover:text-indigo-600">10. 用户管理</a>
                        <a href="#chat" class="block nav-link text-gray-700 hover:text-indigo-600">11. 单聊实现</a>
                    </nav>
                </div>
            </aside>

            <!-- Main Content -->
            <main class="flex-1">
                <!-- What is Netty -->
                <section id="what-is-netty" class="mb-16">
                    <h2 class="text-3xl font-bold mb-6 text-gray-800 border-b pb-2">1. Netty是什么</h2>
                    <div class="prose max-w-none">
                        <p class="text-lg text-gray-700 mb-4">
                            Netty是由JBOSS提供的一个Java开源框架，它不需要运行在Tomcat这类服务器中，而是可以单独构建一个服务器。
                        </p>
                        <div class="bg-blue-50 border-l-4 border-blue-500 p-4 mb-6">
                            <div class="flex">
                                <div class="flex-shrink-0">
                                    <i class="fas fa-info-circle text-blue-500 mt-1"></i>
                                </div>
                                <div class="ml-3">
                                    <p class="text-sm text-blue-700">
                                        Netty可以构建HTTP服务器、Socket服务器、WebSocket服务器等，它其实是对JDK1.4以后提供的NIO的封装。
                                    </p>
                                </div>
                            </div>
                        </div>
                    </div>
                </section>

                <!-- Introduction -->
                <section id="introduction" class="mb-16">
                    <h2 class="text-3xl font-bold mb-6 text-gray-800 border-b pb-2">2. Netty简介</h2>
                    <div class="prose max-w-none">
                        <p class="text-gray-700 mb-6">
                            Netty 是一款异步的事件驱动的网络应用程序框架，支持快速地开发可维护的高性能的面向协议的服务器和客户端，底层利用了NIO的机制。
                        </p>
                        <p class="text-gray-700 mb-6">
                            Java本身也提供各种IO的操作，但是使用起来API会很繁琐，同时性能有很难有保证，经常会出现莫名其妙的bug。为了方便开发者更好地把精力集中于业务，Netty封装了一切繁琐的工作，对开发者透明化，大大降低了开发门槛。
                        </p>
                        <div class="diagram-container">
                            <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1692178879772-0e20040c-2544-4281-82bf-6edec361bd48.png" alt="Netty架构图" class="rounded-lg w-full">
                        </div>
                    </div>
                </section>

                <!-- Important Components -->
                <section id="components" class="mb-16">
                    <h3 class="text-2xl font-bold mb-6 text-gray-800 border-b pb-2">2.1 Netty中的重要组件</h3>
                    <div class="grid md:grid-cols-2 gap-6">
                        <div class="component-card bg-white p-6 rounded-lg shadow-sm">
                            <h4 class="font-bold text-lg text-indigo-600 mb-3">Bootstrap / ServerBootstrap</h4>
                            <p class="text-gray-700">Netty引导组件，简化NIO的开发步骤，是一个Netty程序的开始，作用是配置和串联各个组件。</p>
                        </div>
                        <div class="component-card bg-white p-6 rounded-lg shadow-sm">
                            <h4 class="font-bold text-lg text-indigo-600 mb-3">EventLoopGroup</h4>
                            <p class="text-gray-700">是EventLoop组合，可以包含多个EventLoop。创建一个EventLoopGroup的时候，内部包含的方法就会创建一个子对象EventLoop。</p>
                        </div>
                        <div class="component-card bg-white p-6 rounded-lg shadow-sm">
                            <h4 class="font-bold text-lg text-indigo-600 mb-3">Channel</h4>
                            <p class="text-gray-700">代表一个Socket连接，或者其他IO操作组件。Channel是通讯的载体。</p>
                        </div>
                        <div class="component-card bg-white p-6 rounded-lg shadow-sm">
                            <h4 class="font-bold text-lg text-indigo-600 mb-3">ChannelPipeline</h4>
                            <p class="text-gray-700">主要是管理各种ChannelHandler业务控制器，提供一个链式管理模式。</p>
                        </div>
                        <div class="component-card bg-white p-6 rounded-lg shadow-sm">
                            <h4 class="font-bold text-lg text-indigo-600 mb-3">Decoder / Encoder</h4>
                            <p class="text-gray-700">网络传输都是byte传输，所以Netty首先接收到的是byte，需要进行解码变成JAVA对象（解码），或者编码成byte传输给客户端（编码）。</p>
                        </div>
                        <div class="component-card bg-white p-6 rounded-lg shadow-sm">
                            <h4 class="font-bold text-lg text-indigo-600 mb-3">Future / ChannelFuture</h4>
                            <p class="text-gray-700">Netty提供的返回结果，类似回调函数，告知你执行结果是什么。</p>
                        </div>
                    </div>
                </section>

                <!-- Thread Model -->
                <section id="thread-model" class="mb-16">
                    <h2 class="text-3xl font-bold mb-6 text-gray-800 border-b pb-2">3. Netty线程模型</h2>
                    <div class="grid md:grid-cols-3 gap-6">
                        <div class="bg-white p-6 rounded-lg shadow-sm border-t-4 border-blue-500">
                            <h3 class="font-bold text-lg text-blue-600 mb-3">单线程模型</h3>
                            <p class="text-gray-700">所有的IO操作都在同一个NIO线程上面完成，一个线程既要处理客户端连接还有处理客户端的读写操作。</p>
                        </div>
                        <div class="bg-white p-6 rounded-lg shadow-sm border-t-4 border-purple-500">
                            <h3 class="font-bold text-lg text-purple-600 mb-3">多线程模型</h3>
                            <p class="text-gray-700">与单线程模型最大的区别就是有一组NIO线程(线程池)处理IO操作。</p>
                        </div>
                        <div class="bg-white p-6 rounded-lg shadow-sm border-t-4 border-indigo-500">
                            <h3 class="font-bold text-lg text-indigo-600 mb-3">主从多线程模型</h3>
                            <p class="text-gray-700">服务端有两个线程池，主线程池主要处理客户端的连接，从线程池主要处理客户端的读写。</p>
                        </div>
                    </div>
                </section>

                <!-- Communication Implementation -->
                <section id="communication" class="mb-16">
                    <h2 class="text-3xl font-bold mb-6 text-gray-800 border-b pb-2">4. Netty实现通讯</h2>
                    <h3 class="text-xl font-semibold mb-4 text-gray-700">4.1 依赖</h3>
                    <div class="code-block mb-8">
                        <div class="lang">XML</div>
                        <pre class="text-gray-200"><code>&lt;dependency&gt;
    &lt;groupId&gt;io.netty&lt;/groupId&gt;
    &lt;artifactId&gt;netty-codec-http&lt;/artifactId&gt;
    &lt;version&gt;4.1.39.Final&lt;/version&gt;
&lt;/dependency&gt;</code></pre>
                    </div>

                    <h3 class="text-xl font-semibold mb-4 text-gray-700">4.1 服务端</h3>
                    <div class="code-block mb-8">
                        <div class="lang">Java</div>
                        <pre class="text-gray-200"><code>// 1.创建主从线程池
EventLoopGroup masterEventLoopGroup = new NioEventLoopGroup();
EventLoopGroup slaveEventLoopGroup = new NioEventLoopGroup();
try {
    // 2.创建服务的启动类
    ServerBootstrap bootstrap = new ServerBootstrap();

    // 设置Netty的主从线程模式(可以设置单线程，多线程)
    bootstrap.group(masterEventLoopGroup,slaveEventLoopGroup);

    // 设置Netty中的管道类型
    bootstrap.channel(NioServerSocketChannel.class);

    // 设置客户端消息处理对象
    bootstrap.childHandler(new ServerChannelHandler());

    // 设置绑定的端口, 端口绑定是是个异步操作，所以下面需要阻塞
    ChannelFuture future = bootstrap.bind(8080);

    // 阻塞当前线程，当端口绑定成功后，主线程才会往下执行
    future.sync();
    System.out.println("端口绑定成功。。。");

    // 等待通道被关闭
    future.channel().closeFuture().sync();
} catch (Exception e) {
    e.printStackTrace();
} finally {
    masterEventLoopGroup.shutdownGracefully();
    slaveEventLoopGroup.shutdownGracefully();
}</code></pre>
                    </div>

                    <h3 class="text-xl font-semibold mb-4 text-gray-700">4.2 ServerChannelHandler</h3>
                    <div class="code-block mb-8">
                        <div class="lang">Java</div>
                        <pre class="text-gray-200"><code>@ChannelHandler.Sharable
public class ServerChannelHandler extends SimpleChannelInboundHandler&lt;ByteBuf&gt; {

    /**
     * 客户端发送过来的消息都会交给这个方法处理
     */
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, 
            ByteBuf byteBuf) throws Exception {
        System.out.println("接收到客户端消息："+byteBuf.toString(Charset.defaultCharset()));
        ByteBuf buffer = Unpooled.buffer(1024);
        buffer.writeBytes(byteBuf);
        buffer.writeBytes("_服务端返回".getBytes("utf-8"));
        channelHandlerContext.writeAndFlush(buffer);
    }
}</code></pre>
                    </div>

                    <h3 class="text-xl font-semibold mb-4 text-gray-700">4.3 客户端</h3>
                    <div class="code-block">
                        <div class="lang">Java</div>
                        <pre class="text-gray-200"><code>public static void main(String[] args) throws Exception {
    // 1.创建客户端的线程池
    EventLoopGroup eventExecutors = new NioEventLoopGroup();

    // 2.创建启动类
    try {
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(eventExecutors); // 设置多线程处理
        bootstrap.channel(NioSocketChannel.class); // 设置客户端管道类型
        bootstrap.handler(new SimpleChannelInboundHandler&lt;ByteBuf&gt;() { // 处置客户端的处理类
            @Override
            protected void channelRead0(ChannelHandlerContext channelHandlerContext, 
                    ByteBuf byteBuf) throws Exception {
                System.out.println("客户端接收到的内容："+byteBuf.toString(Charset.defaultCharset()));
            }
        });

        // 3.连接服务端(异步连接服务器)
        ChannelFuture channelFuture = bootstrap.connect("localhost", 8080);
        channelFuture.sync(); // 阻塞
        System.out.println("客户端连接成功。。。");
        while(true) {
            Scanner scanner = new Scanner(System.in);
            String content = scanner.next();
            Channel channel = channelFuture.channel(); // 获取客户端的通道给服务端发消息
            ByteBuf buffer = Unpooled.buffer(1024); // 创建一个ByteBuf
            buffer.writeBytes(content.getBytes("utf-8")); // 把内容写到ByteBuf中
            channel.writeAndFlush(buffer); // 把buff写入到通道
        }
    } finally {
        // 释放资源
        eventExecutors.shutdownGracefully();
    }
}</code></pre>
                    </div>
                </section>

                <!-- ChannelPipeline -->
                <section id="channel-pipeline" class="mb-16">
                    <h2 class="text-3xl font-bold mb-6 text-gray-800 border-b pb-2">5. ChannelPipeline</h2>
                    <div class="prose max-w-none mb-6">
                        <p class="text-gray-700">
                            ChannelPipeline将多个ChannelHandler链接在一起来让事件在其中传播处理。一个ChannelPipeline中可能不仅有入站处理器，还有出站处理器，入站处理器只会处理入站的事件，而出站处理器只会处理出站的数据。
                        </p>
                    </div>
                    <div class="diagram-container">
                        <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1692178880090-7cf2c0ce-6218-48a1-9fec-00ba6c4b4ef3.png" alt="ChannelPipeline示意图" class="rounded-lg w-full">
                    </div>
                    <div class="code-block mt-6">
                        <div class="lang">Java</div>
                        <pre class="text-gray-200"><code>bootstrap.childHandler(new ChannelInitializer() { //提供了一个传输通道
    @Override
    protected void initChannel(Channel channel) throws Exception {
        ChannelPipeline pipeline = channel.pipeline();
        pipeline.addLast(new StringDecoder()); // 解码
        pipeline.addLast(new StringEncoder()); // 编码
        pipeline.addLast(new ServerChannelHandlerIn()); // 处理业务
    }
});</code></pre>
                    </div>
                </section>

                <!-- HTTP Server -->
                <section id="http-server" class="mb-16">
                    <h2 class="text-3xl font-bold mb-6 text-gray-800 border-b pb-2">6. Netty实现HTTP服务器</h2>
                    <h3 class="text-xl font-semibold mb-4 text-gray-700">6.1 HttpServer</h3>
                    <div class="code-block mb-8">
                        <div class="lang">Java</div>
                        <pre class="text-gray-200"><code>public static void main(String[] args) {
    EventLoopGroup masterLoopGroup = new NioEventLoopGroup();
    EventLoopGroup slaveLoopGroup = new NioEventLoopGroup();

    try {
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(masterLoopGroup,slaveLoopGroup);
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.childHandler(new ChannelInitializer() {
            @Override
            protected void initChannel(Channel channel) throws Exception {
                ChannelPipeline pipeline = channel.pipeline();

                // 该解码器会将字符串将字符串转成HttpRequest
                pipeline.addLast(new HttpServerCodec()); // 添加Http的编解码器

                // 该解码器将HttpRequest转成FullHttpRequset的对象
                pipeline.addLast(new HttpObjectAggregator(1024*1024));
                pipeline.addLast(new HttpChannelHander());
            }
        });
        ChannelFuture channelFuture = serverBootstrap.bind("localhost", 8080);
        channelFuture.sync();
        System.out.println("服务启动成功");

        channelFuture.channel().closeFuture().sync();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        masterLoopGroup.shutdownGracefully();
        slaveLoopGroup.shutdownGracefully();
    }
}</code></pre>
                    </div>

                    <h3 class="text-xl font-semibold mb-4 text-gray-700">6.2 ChannelHandler</h3>
                    <div class="code-block">
                        <div class="lang">Java</div>
                        <pre class="text-gray-200"><code>public class HttpChannelHander extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, 
            FullHttpRequest req) throws Exception {
        String method = req.method().name();
        System.out.println("请求方法:"+method);

        HttpHeaders headers = req.headers();
        List&lt;Map.Entry&lt;String, String&gt;&gt; entries = headers.entries();
        for (Map.Entry&lt;String, String&gt; map:entries) {
            System.out.println(map.getKey()+"-"+map.getValue());
        }

        String content = req.content().toString(Charset.defaultCharset());
        System.out.println("内容:"+content);

        // 响应一个页面
        String respMsg = "&lt;html&gt;Hello World&lt;/html&gt;";
        FullHttpResponse resp = new DefaultFullHttpResponse(
            HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
        resp.headers().add("context-type","text/html;charset=utf8");
        resp.content().writeBytes(respMsg.getBytes("utf-8"));
        channelHandlerContext.writeAndFlush(resp);
    }
}</code></pre>
                    </div>
                </section>

                <!-- FTP Server -->
                <section id="ftp-server" class="mb-16">
                    <h2 class="text-3xl font-bold mb-6 text-gray-800 border-b pb-2">7. Netty实现FTP服务器</h2>
                    <h3 class="text-xl font-semibold mb-4 text-gray-700">7.1 HttpServer</h3>
                    <div class="code-block mb-8">
                        <div class="lang">Java</div>
                        <pre class="text-gray-200"><code>serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.childHandler(new ChannelInitializer() {
    @Override
    protected void initChannel(Channel channel) throws Exception {
        ChannelPipeline pipeline = channel.pipeline();

        // 添加分块下载的编码器
        pipeline.addLast(new ChunkedWriteHandler());
        // 该解码器会将字符串将字符串转成HttpRequest
        pipeline.addLast(new HttpServerCodec()); // 添加Http的编解码器
        // 该解码器将HttpRequest转成FullHttpRequset的对象
        pipeline.addLast(new HttpObjectAggregator(1024*1024));
        pipeline.addLast(new FileChannleHandler());
    }
});</code></pre>
                    </div>

                    <h3 class="text-xl font-semibold mb-4 text-gray-700">7.2 ChannelHandler</h3>
                    <div class="code-block">
                        <div class="lang">Java</div>
                        <pre class="text-gray-200"><code>public class FileChannleHandler extends SimpleChannelInboundHandler&lt;FullHttpRequest&gt; {

    private String filesPath = "F:\\soft_exe";

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, 
            FullHttpRequest request) throws Exception {

        // http://localhost:8080/abc?name
        String uri = request.uri();
        String method = request.method().name();

        if (!"GET".equals(method)) {
            respError(channelHandlerContext, "支持GET请求");
            return;
        }

        String fpath = filesPath + uri;
        File file = new File(fpath);

        if(!file.exists()){
            respError(channelHandlerContext, "文件不存在。。");
            return;
        }

        if(file.isFile()){
            System.out.println("文件");
            respFile(channelHandlerContext,file);
        } else {
            respDirectory(channelHandlerContext,file,uri);
        }
    }

    private void respFile(ChannelHandlerContext channelHandlerContext, File file) throws Exception {
        RandomAccessFile df = new RandomAccessFile(file,"r");

        FullHttpResponse resp = new DefaultFullHttpResponse(
            HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
        resp.headers().add("Content-Type","tapplication/octet-stream");
        resp.headers().add("Content-Length",df.length());

        // 先返回响应的内容
        channelHandlerContext.writeAndFlush(resp);

        // 返回要下载的文件
        // 分块下载
        ChunkedNioFile nioFile = new ChunkedNioFile(df.getChannel(),1024);
        ChannelFuture channelFuture = channelHandlerContext.writeAndFlush(nioFile);
        System.out.println("下载完成");
        channelHandlerContext.close();
    }

    private void respDirectory(ChannelHandlerContext channelHandlerContext, File file, String uri) {
        FullHttpResponse resp = new DefaultFullHttpResponse(
            HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
        resp.headers().add("Content-Type","text/html;charset=utf8");
        String html = "&lt;html&gt;";
        File[] files = file.listFiles();
        System.out.println(uri);
        for(int i =0;i&lt;files.length;i++){
            if(!"/".equals(uri)){
                html+="&lt;li&gt; &lt;a href = '"+uri+File.separator+files[i].getName()+"'&gt;"+
                    files[i].getName()+"&lt;a&gt;&lt;/li&gt;";
            } else {
                html+="&lt;li&gt; &lt;a href = '"+File.separator+files[i].getName()+"'&gt;"+
                    files[i].getName()+"&lt;a&gt;&lt;/li&gt;";
            }
        }
        html +="&lt;/html&gt;";
        try {
            resp.content().writeBytes(html.getBytes("utf-8"));
            channelHandlerContext.writeAndFlush(resp);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    private void respError(ChannelHandlerContext channelHandlerContext, String errorMsg) {
        FullHttpResponse resp = new DefaultFullHttpResponse(
            HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
        resp.headers().add("Content-type", "text/html;charset=utf8");
        try {
            resp.content().writeBytes(errorMsg.getBytes("utf-8"));
            channelHandlerContext.writeAndFlush(resp);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
}</code></pre>
                    </div>
                </section>

                <!-- WebSocket -->
                <section id="websocket" class="mb-16">
                    <h2 class="text-3xl font-bold mb-6 text-gray-800 border-b pb-2">8. Netty实现WebSocket</h2>
                    <div class="prose max-w-none mb-6">
                        <h3 class="text-xl font-semibold mb-4 text-gray-700">8.1 什么是WebSocket</h3>
                        <p class="text-gray-700 mb-4">
                            我们一直使用的http协议只能由客户端发起，服务端无法直接进行推送，这就导致了如果服务端有持续的变化客户端想要获知就比较麻烦。WebSocket协议就是为了解决这个问题应运而生。WebSocket协议，客户端和服务端都可以主动的推送消息，可以是文本也可以是二进制数据。而且没有同源策略的限制，不存在跨域问题。协议的标识符就是ws。
                        </p>
                        <p class="text-gray-700">
                            WebSocket是H5之后提供的一种网络通讯技术，属于应用层协议。它基于 TCP 传输协议，并复用 HTTP 的握手通道。
                        </p>
                    </div>

                    <h3 class="text-xl font-semibold mb-4 text-gray-700">8.2 WebSocket 帧</h3>
                    <div class="grid md:grid-cols-2 gap-6 mb-8">
                        <div class="bg-white p-6 rounded-lg shadow-sm">
                            <h4 class="font-bold text-lg text-indigo-600 mb-3">数据帧 -- 用来传递数据</h4>
                            <ul class="list-disc pl-5 text-gray-700 space-y-1">
                                <li>TextWebSocketFrame：文本帧</li>
                                <li>BinaryWebSocketFrame：二进制帧</li>
                            </ul>
                        </div>
                        <div class="bg-white p-6 rounded-lg shadow-sm">
                            <h4 class="font-bold text-lg text-indigo-600 mb-3">状态帧 -- 检测心跳</h4>
                            <ul class="list-disc pl-5 text-gray-700 space-y-1">
                                <li>PingWebSocketFrame：ping帧(客户端发送ping帧)</li>
                                <li>PongWebSocketFrame：pong帧(服务端响应pong帧)</li>
                                <li>CloseWebSocketFrame：关闭帧</li>
                            </ul>
                        </div>
                    </div>

                    <h3 class="text-xl font-semibold mb-4 text-gray-700">8.3 WebSocket 通讯</h3>
                    <div class="grid md:grid-cols-2 gap-6 mb-8">
                        <div>
                            <h4 class="font-medium text-lg mb-2 text-gray-700">服务端</h4>
                            <div class="code-block">
                                <div class="lang">Java</div>
                                <pre class="text-gray-200"><code>serverBootstrap.childHandler(new ChannelInitializer() {
    @Override
    protected void initChannel(Channel channel) throws Exception {
        ChannelPipeline pipeline = channel.pipeline();

        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new HttpObjectAggregator(1024*1024));

        // 添加WebSocket的编解码器
        pipeline.addLast(new WebSocketServerProtocolHandler("/"));
        pipeline.addLast(new WebSocketChannelHandler());
    }
});</code></pre>
                            </div>
                        </div>
                        <div>
                            <h4 class="font-medium text-lg mb-2 text-gray-700">WebSocketChannelHandler</h4>
                            <div class="code-block">
                                <div class="lang">Java</div>
                                <pre class="text-gray-200"><code>public class WebSocketChannelHandler extends 
        SimpleChannelInboundHandler&lt;TextWebSocketFrame&gt; {
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, 
            TextWebSocketFrame textWebSocketFrame) throws Exception {
        System.out.println("WebSocketChannelHandler.channelRead0:"+
            textWebSocketFrame.text());
        TextWebSocketFrame respFrame = new TextWebSocketFrame("嗯！");
        channelHandlerContext.writeAndFlush(respFrame);
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("新的客户端连接。。。");
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端断开。。。");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("客户端非正常断开。。。");
    }
}</code></pre>
                            </div>
                        </div>
                    </div>

                    <div class="mb-8">
                        <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1692178880327-1a0a71eb-ec66-4aa3-831c-1775587bf4fb.png" alt="WebSocket客户端" class="rounded-lg w-full max-w-md mx-auto">
                    </div>

                    <h3 class="text-xl font-semibold mb-4 text-gray-700">8.4 WebSocket 心跳检测</h3>
                    <div class="grid md:grid-cols-2 gap-6 mb-8">
                        <div>
                            <h4 class="font-medium text-lg mb-2 text-gray-700">服务端</h4>
                            <div class="code-block mb-4">
                                <div class="lang">Java</div>
                                <pre class="text-gray-200"><code>// 心跳检测
pipeline.addLast(new IdleStateHandler(10, 0, 0, TimeUnit.SECONDS));
pipeline.addLast(new HeartBeatHandler());</code></pre>
                            </div>
                            <div class="code-block">
                                <div class="lang">Java</div>
                                <pre class="text-gray-200"><code>public class HeartBeatHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if(evt instanceof IdleStateEvent){
            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
            if(idleStateEvent.state() == IdleState.READER_IDLE){
                System.out.println("读空闲。。。");
                ctx.channel().close();
            }
        }
    }
}</code></pre>
                            </div>
                        </div>
                        <div>
                            <h4 class="font-medium text-lg mb-2 text-gray-700">客户端</h4>
                            <div class="code-block">
                                <div class="lang">JavaScript</div>
                                <pre class="text-gray-200"><code>var sendHeadFun;
function sendHear(){
    if(websocket){
        var hear ={"type":"2"};
        sendHeadFun = setInterval(function(){
            sendObj(hear);
        },5000)
    }
}

// 重连方法
function reConn(event){
    setTimeout(function() {
        ws.init(event);
    }, 5000);
}</code></pre>
                            </div>
                        </div>
                    </div>

                    <h3 class="text-xl font-semibold mb-4 text-gray-700">8.5 客户端离线后重连</h3>
                    <div class="code-block">
                        <div class="lang">JavaScript</div>
                        <pre class="text-gray-200"><code>// 断线监听
websocket.onclose = function(e){
    clearInterval(sendHeadFun); // 客户端断开连接后停止发送心跳
    reConn(event);// 客户端断开后尝试重新连接
    event.onclose();
}</code></pre>
                    </div>
                </section>

                <!-- SpringBoot Integration -->
                <section id="springboot" class="mb-16">
                    <h2 class="text-3xl font-bold mb-6 text-gray-800 border-b pb-2">9. Netty 整合SpringBoot搭建通讯服务器</h2>
                    <h3 class="text-xl font-semibold mb-4 text-gray-700">9.1 Netty服务启动</h3>
                    <div class="code-block mb-8">
                        <div class="lang">Java</div>
                        <pre class="text-gray-200"><code>@Component
public class SpringBootStartHandler implements CommandLineRunner {

    @Value("${netty.port}")
    private Integer port;

    @Override
    public void run(String... args) throws Exception {
        EventLoopGroup master = new NioEventLoopGroup();
        EventLoopGroup slave = new NioEventLoopGroup();

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(master,slave);
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.childHandler(new ChannelInitializer() {
            @Override
            protected void initChannel(Channel channel) throws Exception {
                ChannelPipeline pipeline = channel.pipeline();
                pipeline.addLast(new HttpServerCodec());
                pipeline.addLast(new HttpObjectAggregator(1024*1024));
                pipeline.addLast(new WebSocketServerProtocolHandler("/"));

                // 接收客户端请求
                pipeline.addLast(new WebSocketMasgHanlder());

                // 处理连接消息
                pipeline.addLast(new ConnMsgHandler());

                // 处理心跳消息
                pipeline.addLast(new HeardMsgHandler());
            }
        });
        ChannelFuture future = serverBootstrap.bind(port);
        future.sync();
        System.out.println("netty服务启动成功");
    }
}</code></pre>
                    </div>

                    <h3 class="text-xl font-semibold mb-4 text-gray-700">9.2 WebSocketMasgHanlder</h3>
                    <div class="code-block mb-8">
                        <div class="lang">Java</div>
                        <pre class="text-gray-200"><code>public class WebSocketMasgHanlder extends SimpleChannelInboundHandler&lt;TextWebSocketFrame&gt; {
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, 
            TextWebSocketFrame textWebSocketFrame) throws Exception {
        String text = textWebSocketFrame.text();
        System.out.println("客户端消息："+ text);
        Gson gson = new Gson();
        NettyMsg nettyMsg = gson.fromJson(text, NettyMsg.class);
        System.out.println(nettyMsg);
        if(nettyMsg.getType() == 1){ // 1 连接
            nettyMsg =gson.fromJson(text, ConnMsg.class);
        } else if (nettyMsg.getType()== 2){ // 2.心跳
            nettyMsg =gson.fromJson(text, HeardMsg.class);
        }
        // 接着往下传，交给下一个处理器处理
        channelHandlerContext.fireChannelRead(nettyMsg);
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("新客户端连接。。。");
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端断开。。。");
        // 客户端断开后重Map中移除
        ChannelGroup.removeChannelByChannel(ctx.channel());
    }
}</code></pre>
                    </div>

                    <h3 class="text-xl font-semibold mb-4 text-gray-700">9.3 HeardMsgHandler</h3>
                    <div class="code-block mb-8">
                        <div class="lang">Java</div>
                        <pre class="text-gray-200"><code>public class HeardMsgHandler extends SimpleChannelInboundHandler&lt;HeardMsg&gt; {
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, 
            HeardMsg heardMsg) throws Exception {
        System.out.println("处理心跳的消息。。。:"+heardMsg);
        // 回复心跳
        TextWebSocketFrame textWebSocketFrame = new TextWebSocketFrame("heard");
        channelHandlerContext.writeAndFlush(textWebSocketFrame);
    }
}</code></pre>
                    </div>

                    <h3 class="text-xl font-semibold mb-4 text-gray-700">9.4 ConnMsgHandler</h3>
                    <div class="code-block mb-8">
                        <div class="lang">Java</div>
                        <pre class="text-gray-200"><code>public class ConnMsgHandler extends SimpleChannelInboundHandler&lt;ConnMsg&gt; {
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, 
            ConnMsg connMsg) throws Exception {
        System.out.println("处理连接的消息。。。"+connMsg);
        // 新的连接保存起来
        String did = connMsg.getDid();
        ChannelGroup.addChannel(did,channelHandlerContext.channel());
    }
}</code></pre>
                    </div>

                    <h3 class="text-xl font-semibold mb-4 text-gray-700">9.5 ChannelGroup</h3>
                    <div class="code-block mb-8">
                        <div class="lang">Java</div>
                        <pre class="text-gray-200"><code>public class ChannelGroup {
    public static Map&lt;String,Channel&gt; channelMap = new HashMap&lt;&gt;();

    public static Channel addChannel(String did,Channel channel){
        return channelMap.put(did,channel);
    }

    public static Channel getChannel(String did){
        return channelMap.get(did);
    }

    public static void removeChannelByDid(String did){
        channelMap.remove(did);
    }

    public static void removeChannelByChannel(Channel channel){
        if(channelMap.containsValue(channel)){
            Set&lt;Map.Entry&lt;String, Channel&gt;&gt; entries = channelMap.entrySet();
            for (Map.Entry&lt;String, Channel&gt; ch :entries) {
                if(ch.getValue() == channel){
                    channelMap.remove(ch.getKey());
                    break;
                }
            }
        }
    }
}</code></pre>
                    </div>

                    <h3 class="text-xl font-semibold mb-4 text-gray-700">9.7 WebSocket.js</h3>
                    <div class="code-block mb-8">
                        <div class="lang">JavaScript</div>
                        <pre class="text-gray-200"><code>var websocket;

window.ws={
    init:function(event){
        if(window.WebSocket){
            websocket = new WebSocket("ws://192.168.0.106:8085");

            websocket.onopen = function(){
                sendHear(); // 客户端连接后发送就开始发送心跳
                //closeCoon(); // 关闭连接
                event.onopen();
            }

            websocket.onclose = function(e){
                clearInterval(sendHeadFun); // 客户端断开连接后停止发送心跳
                reConn(event);// 客户端断开后尝试重新连接
                event.onclose();
            }

            websocket.onerror = function(){
                console.info("服务端出现异常..")
            }

            websocket.onmessage = function(data){
                // 收到的是心跳不显示给客户端
                if(data.data == "heard"){
                    clearTimeout(closeCoonTime);
                    closeCoon();
                    return ;
                }
                event.onmessage(data.data);
            }
        } else {
            alert("当前浏览器不支持WebSocket");
        }
    }
}

function send(msg){
    websocket.send(msg);
}

function sendObj(obj){
    websocket.send(JSON.stringify(obj))
}

var closeCoonTime;
function closeCoon(){
    if(websocket){
        closeCoonTime = setTimeout(function(){
            websocket.close();
        },10000);
    }
}

var sendHeadFun;
function sendHear(){
    if(websocket){
        var hear ={"type":"2"};
        sendHeadFun = setInterval(function(){
            sendObj(hear);
        },5000)
    }
}

// 重连方法
function reConn(event){
    setTimeout(function() {
        ws.init(event);
    }, 5000);
}</code></pre>
                    </div>

                    <h3 class="text-xl font-semibold mb-4 text-gray-700">9.8 index.html</h3>
                    <div class="mb-8">
                        <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1692178882129-3a38ae38-47b4-4be7-adf3-6a966c6dadf9.png" alt="WebSocket客户端界面" class="rounded-lg w-full max-w-md mx-auto">
                    </div>
                </section>

                <!-- User Management -->
                <section id="user-management" class="mb-16">
                    <h2 class="text-3xl font-bold mb-6 text-gray-800 border-b pb-2">10. 用户挤下线</h2>
                    <h3 class="text-xl font-semibold mb-4 text-gray-700">10.1.用户登录状态</h3>
                    <div class="grid md:grid-cols-2 gap-6 mb-8">
                        <div>
                            <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1692178882408-31912199-b986-44cf-a849-3425da43e5f8.png" alt="用户登录状态管理" class="rounded-lg">
                        </div>
                        <div>
                            <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1692178882651-67a02ead-c9f3-4cef-b61b-affc8435b794.png" alt="Redis存储设备ID" class="rounded-lg">
                        </div>
                    </div>

                    <h3 class="text-xl font-semibold mb-4 text-gray-700">10.2 Netty服务器</h3>
                    <h4 class="font-medium text-lg mb-2 text-gray-700">10.2.1创建队列和交换机</h4>
                    <div class="code-block mb-8">
                        <div class="lang">Java</div>
                        <pre class="text-gray-200"><code>@Configuration
public class RabbitMQConfig {

    @Value("${netty.port}")
    private String port;

    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("chat_exchange");
    }

    @Bean
    public Queue chatQueue(){
        return new Queue("chat_"+port+"_queue");
    }

    @Bean
    public Binding chatQueueBindingCartExchange(){
        return BindingBuilder.bind(chatQueue()).to(fanoutExchange());
    }
}</code></pre>
                    </div>

                    <h4 class="font-medium text-lg mb-2 text-gray-700">10.2.2监听队列发送退出的消息</h4>
                    <div class="code-block mb-8">
                        <div class="lang">Java</div>
                        <pre class="text-gray-200"><code>@Component
public class ChatQueueListener {

    @RabbitListener(queues = "chat_${netty.port}_queue")
    public void handlerChat(ShutdownMsg shutdownMsg){
        String did = shutdownMsg.getDid();

        // 根据设备号获取客户端管道对象
        Channel channel = ChannelGroup.getChannel(did);
        if(channel != null){
            // 把对象转成文本帧输出
            TextWebSocketFrame frame = new TextWebSocketFrame(new Gson().toJson(shutdownMsg));
            // 把挤下线的对象响应给客户端
            channel.writeAndFlush(frame);
        }
    }
}</code></pre>
                    </div>

                    <h4 class="font-medium text-lg mb-2 text-gray-700">10.2.3 客户端收到消息后退出</h4>
                    <div class="mb-8">
                        <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1692178882838-afcae86b-3c97-4a41-a815-c7b5b56cb4ed.png" alt="客户端处理下线消息" class="rounded-lg">
                    </div>

                    <h4 class="font-medium text-lg mb-2 text-gray-700">10.2.4 客户端发消息就判断是否被挤下线</h4>
                    <div class="code-block">
                        <div class="lang">Java</div>
                        <pre class="text-gray-200"><code>public class ConnMsgHandler extends SimpleChannelInboundHandler&lt;ConnMsg&gt; {

    private StringRedisTemplate redisTemplate;

    public ConnMsgHandler(StringRedisTemplate redisTemplate){
        this.redisTemplate = redisTemplate;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, 
            ConnMsg connMsg) throws Exception {
        // 新的连接保存起来
        String did = connMsg.getDid();
        Integer uid = connMsg.getUid();
        ChannelGroup.addChannel(did,channelHandlerContext.channel());
        System.out.println("处理连接的消息。。。"+connMsg);
        // 用户连接netty服务器后判断当前的设备号号Redis的设备号是否一致，如果不一致说明已经在其他设备上登录过
        String redisDid = redisTemplate.opsForValue().get(uid.toString());
        if(redisDid != null && !redisDid.equals(did)){
            ShutdownMsg shutdownMsg = new ShutdownMsg();
            shutdownMsg.setDid(redisDid);
            TextWebSocketFrame frame = new TextWebSocketFrame(new Gson().toJson(shutdownMsg));
            channelHandlerContext.writeAndFlush(frame);
        }
    }
}</code></pre>
                    </div>
                </section>

                <!-- Chat Implementation -->
                <section id="chat" class="mb-16">
                    <h2 class="text-3xl font-bold mb-6 text-gray-800 border-b pb-2">11. 用户单聊</h2>
                    <h3 class="text-xl font-semibold mb-4 text-gray-700">11.1 发送用户消息</h3>
                    <div class="mb-8">
                        <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1692178883176-17d64e97-c50c-4311-afd6-e18b322e9e9d.png" alt="发送消息界面" class="rounded-lg">
                    </div>

                    <h3 class="text-xl font-semibold mb-4 text-gray-700">11.2 Handler中分配</h3>
                    <div class="mb-8">
                        <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1692178883426-1c5cb92f-6a25-402c-95b2-afcf3676000a.png" alt="消息处理器" class="rounded-lg">
                    </div>

                    <h3 class="text-xl font-semibold mb-4 text-gray-700">11.3 消息处理器中把消息交给交换机</h3>
                    <div class="mb-8">
                        <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1692178883637-7e1c8cc5-a649-4cd8-9efd-4e0d6b583ecc.png" alt="消息处理流程" class="rounded-lg">
                    </div>

                    <h3 class="text-xl font-semibold mb-4 text-gray-700">11.4 处理消息队列</h3>
                    <div class="mb-8">
                        <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1692178883855-2cbecb0e-ea03-42ef-bd9e-63a590e87ca0.png" alt="消息队列处理" class="rounded-lg">
                    </div>

                    <h3 class="text-xl font-semibold mb-4 text-gray-700">11.5 好友显示消息</h3>
                    <div class="grid md:grid-cols-2 gap-6 mb-8">
                        <div>
                            <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1692178884131-79177252-fd31-4f86-bf4f-75bba616c6fe.png" alt="消息接收界面" class="rounded-lg">
                        </div>
                        <div>
                            <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1692178884435-2632e3c3-c325-4eab-b2b7-7b8d607ee0a5.png" alt="消息显示" class="rounded-lg">
                        </div>
                    </div>

                    <h3 class="text-xl font-semibold mb-4 text-gray-700">12. 保存聊天信息</h3>
                    <h4 class="font-medium text-lg mb-2 text-gray-700">12.1 保存信息</h4>
                    <div class="mb-4">
                        <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1692178884690-a5f44944-48ee-4ca6-b2f5-523257536348.png" alt="消息存储设计" class="rounded-lg">
                    </div>

                    <h4 class="font-medium text-lg mb-2 text-gray-700">12.2 发送或收到消息后保存消息</h4>
                    <div class="grid md:grid-cols-2 gap-6 mb-8">
                        <div>
                            <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1692178884893-5d8575f7-ec4b-4937-aa1e-342de6658299.png" alt="发送消息保存" class="rounded-lg">
                        </div>
                        <div>
                            <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1692178885156-1103d86f-0e8f-407e-b502-e4712bd9fded.png" alt="接收消息保存" class="rounded-lg">
                        </div>
                    </div>

                    <h4 class="font-medium text-lg mb-2 text-gray-700">12.3 进入聊天页面后读取信息</h4>
                    <div class="mb-8">
                        <img src="https://cdn.nlark.com/yuque/0/2023/png/21449790/1692178885343-0f4d51d8-e512-4916-9598-686dfef5b0e6.png" alt="消息读取" class="rounded-lg">
                    </div>
                </section>
            </main>
        </div>
    </div>

    <!-- Footer -->
    <footer class="bg-gray-900 text-white py-8">
        <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
            <div class="text-center">
                <h3 class="text-lg font-medium mb-2">技术小馆</h3>
                <a href="http://www.yuque.com/jtostring" class="text-gray-400 hover:text-white transition duration-300">
                    http://www.yuque.com/jtostring
                </a>
            </div>
        </div>
    </footer>

    <script src="https://cdn.jsdelivr.net/npm/mermaid@latest/dist/mermaid.min.js"></script>
    <script>
        mermaid.initialize({
            startOnLoad: true,
            theme: 'default',
            flowchart: {
                useMaxWidth: true,
                htmlLabels: true,
                curve: 'basis'
            }
        });
    </script>
</body>
</html>
```